﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace UnityModule
{
    public sealed partial class ResourceComponent
    {
        public static float CLEAR_CYCLE = 180;//清理周期

        private UpdateSettings m_UpdateSettings;
        private float lastClearTime = 0;
        private float m_LastOnLowMemeoryEventTime = 0f;

        /// <summary>
        /// 资源缓存列表
        /// </summary>
        /// <typeparam name="string">bundlename-resname</typeparam>
        /// <typeparam name="LoadedRes">缓存的资源</typeparam>
        /// <returns></returns>
        private Dictionary<string, LoadedRes> dicRes = new Dictionary<string, LoadedRes>();

        //版本控制
        private VersionComponent VersionCom
        {
            get;
            set;
        }
#if !UNITY_IOS || HUpdate
        //下载资源
        private DownloadComponent DownloadCom
        {
            get;
            set;
        }
#endif

        //资源引用
        private AssetBundleComponent AssetBundleCom
        {
            get;
            set;
        }

        //资源管理器初始化
        private void Init_Internal()
        {
            m_UpdateSettings = UpdateSettings.Instance;
            Application.lowMemory -= OnLowMemory;
            Application.lowMemory += OnLowMemory;

            VersionCom = VersionComponent.Instance;
            VersionCom.Init();

            AssetBundleCom = AssetBundleComponent.Instance;
            AssetBundleCom.Init();


#if !UNITY_IOS || HUpdate
            DownloadCom = DownloadComponent.Instance;
            DownloadCom.Init();
             if (UpdateSettings.GameSetting.emResType == ResourceMode.EditorLocalRes)
            {
                LoadPrelaodRes(OnLoadPrelaodResFinish);
            }
            else {
                DownloadCom.LoadLocalRes(OnLocalResFinish);
            }
         }

            private void OnLocalResFinish() {
                //Debug.Log("ResourceComponent OnLocalResFinish");
                PanelLoad.Instance.ShowTxt("检测版本");
                VersionCom.StartCheck(OnVersionCheckFinish);
            }

            //检测版本完成
            private void OnVersionCheckFinish()
            {
                PanelLoad.Instance.SetProgress(1);
                //Debug.Log("ResourceComponent OnVersionCheckFinish" + VersionCom.m_getCdnUrl);
                if(VersionCom.m_getCdnUrl)
                {
                    PanelLoad.Instance.ShowTxt("对比资源包差异");
                    //资源下载
                    DownloadCom.StartDownload(OnDownloadFinish);
                }
                else
                {
                    PanelLoad.Instance.ShowTxt("加载资源");
                    //预加载
                    LoadPrelaodRes(OnLoadPrelaodResFinish);
                }
            }

            //资源下载完成
            private void OnDownloadFinish()
            {
                PanelLoad.Instance.ShowTxt("加载资源");                       
                //Debug.Log("ResourceComponent OnDownloadFinish");
                if(DownloadCom.m_failCount == 0)
                {
                    VersionCom.ClearOldBundle(VersionCom.CdnBundleInfosWithHash);
                    VersionCom.SaveBundleAndVersion();
                }
                else
                {
                    VersionCom.ReplaceCdnInfo();
                }
                //预加载
                LoadPrelaodRes(OnLoadPrelaodResFinish);
            }
#else
            if (UpdateSettings.GameSetting.emResType == ResourceMode.EditorLocalRes)
            {
                LoadPrelaodRes(OnLoadPrelaodResFinish);
            }
            else
            {
                StartCoroutine(VersionComponent.LoadBundleBinInfo(ApplicationPath.WWWStreamingAssetsABPath + ApplicationPath.PlatformName + ".bin",
                    (bundleBinInfo, data) =>
                    {
                        VersionCom.IosCopyLocalResToCdnBundle(bundleBinInfo);
                        LoadPrelaodRes(OnLoadPrelaodResFinish);
                    }, (error) =>
                    {
                        Debug.LogError(ApplicationPath.PlatformName + ".bin not found in StreamingAsset");
                    }));
            }
        }
        
#endif

        //预加载完成
        private void OnLoadPrelaodResFinish()
        {
            //Debug.Log("ResourceComponent OnLoadPrelaodResFinish");
            //全部完成回调
            m_onInitFinish?.Invoke();
        }

        //---------- private ----------
#if UNITY_EDITOR
        private void GetResForSimulation(string abName, string resName, Action<LoadedRes> callBack, LoadOptions options = LoadOptions.Auto)
        {
            if ((options & LoadOptions.Scene) == LoadOptions.Scene)
            {
                LoadSceneForSimulation(abName, resName, callBack, options);
            }
            else
            {
                if ((options & LoadOptions.LowPriority) == LoadOptions.LowPriority)
                {
                    StartCoroutine(GetResForSimulationAsync(abName, resName, callBack, options));
                }
                else
                {
                    GetResForSimulationSync(abName, resName, callBack, options);
                }
            }
        }
        private void LoadSceneForSimulation(string abName, string resName, Action<LoadedRes> callBack, LoadOptions options = LoadOptions.Auto)
        {
            LoadedRes tempObj = new LoadedRes();
            tempObj.options = options;

            string[] assetPath = GetSimulationAssetPaths(abName, resName);

            LoadSceneParameters loadSceneParameters;
            if ((options & LoadOptions.SceneAdditive) == LoadOptions.SceneAdditive)
            {
                loadSceneParameters = new LoadSceneParameters(LoadSceneMode.Additive);
            }
            else
            {
                loadSceneParameters = new LoadSceneParameters(LoadSceneMode.Single);
            }
            if (assetPath.Length > 0)
            {
                tempObj.sceneOperation = UnityEditor.SceneManagement.EditorSceneManager.LoadSceneAsyncInPlayMode(assetPath[0], loadSceneParameters);
            }
            else
            {
                if (System.IO.File.Exists(Application.dataPath + "/Scenes/" + resName + ".unity"))
                {
                    tempObj.sceneOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(resName, loadSceneParameters);
                }
                else
                {
                    Debug.LogError("场景资源路径不正确 : " + resName);
                    callBack(null);
                    return;
                }
            }

            callBack(tempObj);
        }
        private void GetResForSimulationSync(string abName, string resName, Action<LoadedRes> callBack, LoadOptions options = LoadOptions.Auto)
        {
            LoadedRes tempObj = null;
            string[] assetPath = GetSimulationAssetPaths(abName, resName);
            if (assetPath.Length > 0)
            {
                if ((LoadOptions.Sprites & options) == LoadOptions.Sprites)
                {
                    SpriteAtlas atlas = new SpriteAtlas();
                    for (int j = 0; j < assetPath.Length; j++)
                    {
                        if (assetPath[j].Contains(resName))
                        {
                            UnityEngine.Object[] sps = UnityEditor.AssetDatabase.LoadAllAssetRepresentationsAtPath(assetPath[j]);
                            if (null != sps)
                            {
                                for (int i = sps.Length - 1; i >= 0; i--)
                                {
                                    atlas.AddSprite(sps[i].name, sps[i] as Sprite);
                                }
                            }
                            break;
                        }
                    }
                    tempObj = new LoadedRes();
                    tempObj.sprites = atlas;
                }
                else
                {
                    UnityEngine.Object t = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath[0]);

                    if(t is Texture2D)
                    {
                        var import = UnityEditor.AssetImporter.GetAtPath(assetPath[0]) as UnityEditor.TextureImporter;
                        if(import.textureType == UnityEditor.TextureImporterType.Sprite)
                        {
                            t = UnityEditor.AssetDatabase.LoadAssetAtPath<Sprite>(assetPath[0]);
                        }
                    }
                    if (null != t)
                    {
                        tempObj = new LoadedRes();
                        tempObj.obj = t;
                    }
                }
            }
            if (null == tempObj)
            {
                P.E("ABName----" + abName + "     " + "ResName----" + resName + "----没有加载到!请检查是否设置AB或者资源是否存在!!!!!");
            }
            CacheRes(abName, resName, tempObj, options);
            if (null != callBack) callBack(tempObj);
        }
        private IEnumerator GetResForSimulationAsync(string abName, string resName, Action<LoadedRes> callBack, LoadOptions options = LoadOptions.Auto)
        {
            yield return new WaitForSeconds(UnityEngine.Random.Range(0.01f, 0.03f));
            GetResForSimulationSync(abName, resName, callBack, options);
        }

        private string[] GetSimulationAssetPaths(string abName, string resName)
        {
            string[] assetPath = UnityEditor.AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(abName, resName);
            return assetPath;
        }
#endif
        /// <summary>
        /// 同步加载资源
        /// </summary>
        /// <param name="abName"></param>
        /// <param name="resName"></param>
        /// <returns></returns>
        private void GetRes(string abName, string resName, Action<LoadedRes> callBack, LoadOptions options = LoadOptions.Auto)
        {
            AssetBundleCom.GetBundle(abName, (loadedBundle) =>
            {
                if (null == loadedBundle || null == loadedBundle.assetBundle)
                {
                    callBack(null);
                    return;
                }
                if ((options & LoadOptions.Scene) == LoadOptions.Scene)
                {
                    LoadScene(resName, callBack, options);
                }
                else if ((options & LoadOptions.SceneSync) == LoadOptions.SceneSync)
                {
                    LoadSceneSync(resName, callBack, options);
                }
                else
                {
                   
                    if ((options & LoadOptions.LowPriority) == LoadOptions.LowPriority)
                    {
                        StartCoroutine(LoadResAsync(loadedBundle, abName, resName, callBack, options));
                    }
                    else
                    {
                        LoadResSync(loadedBundle, abName, resName, callBack, options);
                    }
                }
            }, options);
        }
        private void LoadScene(string resName, Action<LoadedRes> callBack, LoadOptions options = LoadOptions.Auto)
        {
            LoadSceneParameters loadSceneParameters;
            if ((options & LoadOptions.SceneAdditive) == LoadOptions.SceneAdditive)
            {
                loadSceneParameters = new LoadSceneParameters(LoadSceneMode.Additive);
            }
            else
            {
                loadSceneParameters = new LoadSceneParameters(LoadSceneMode.Single);
            }

            LoadedRes tempObj = new LoadedRes();
            tempObj.options = options;
            tempObj.sceneOperation = SceneManager.LoadSceneAsync(resName, loadSceneParameters);
            callBack(tempObj);
        }

        private void LoadSceneSync(string resName, Action<LoadedRes> callBack, LoadOptions options = LoadOptions.Auto)
        {
            LoadSceneParameters loadSceneParameters;
            if ((options & LoadOptions.SceneAdditive) == LoadOptions.SceneAdditive)
            {
                loadSceneParameters = new LoadSceneParameters(LoadSceneMode.Additive);
            }
            else
            {
                loadSceneParameters = new LoadSceneParameters(LoadSceneMode.Single);
            }

            LoadedRes tempObj = new LoadedRes();
            tempObj.options = options;            
            SceneManager.LoadScene(resName, loadSceneParameters);
            callBack(tempObj);
        }

        

        private void LoadResSync(LoadedAssetBundle loadedBundle, string abName, string resName, Action<LoadedRes> callBack, LoadOptions options)
        {
            LoadedRes loadedRes = GetCachedRes(abName, resName);
            if (null == loadedRes)
            {
                if ((options & LoadOptions.Sprites) == LoadOptions.Sprites)
                {
                    SpriteAtlas atlas = null;
                    Sprite[] sps = loadedBundle.assetBundle.LoadAssetWithSubAssets<Sprite>(resName);
                    if (null != sps)
                    {
                        atlas = new SpriteAtlas();
                        for (int i = sps.Length - 1; i >= 0; i--)
                        {
                            atlas.AddSprite(sps[i].name, sps[i]);
                        }
                        loadedRes = new LoadedRes();
                        loadedRes.sprites = atlas;
                    }
                    callBack(loadedRes);
                }
                else
                {
                    UnityEngine.Object tempObj = loadedBundle.assetBundle.LoadAsset<UnityEngine.Object>(resName);
                    if (null != tempObj)
                    {
                        loadedRes = new LoadedRes();
                        loadedRes.obj = tempObj;
                    }
                    callBack(loadedRes);
                }
                CacheRes(abName, resName, loadedRes, options);
            }
            else
            {
                callBack(loadedRes);
            }
        }
        private IEnumerator LoadResAsync(LoadedAssetBundle loadedBundle, string abName, string resName, Action<LoadedRes> callBack, LoadOptions options)
        {
            LoadedRes loadedRes = GetCachedRes(abName, resName);
            if (null == loadedRes)
            {
                AssetBundleRequest request = null;
                if ((options & LoadOptions.Sprites) == LoadOptions.Sprites)
                {
                    request = loadedBundle.assetBundle.LoadAssetWithSubAssetsAsync<Sprite>(resName);
                }
                else
                {
                    request = loadedBundle.assetBundle.LoadAssetAsync(resName);
                }
                yield return request;
                if ((options & LoadOptions.Sprites) == LoadOptions.Sprites)
                {
                    SpriteAtlas atlas = null;
                    UnityEngine.Object[] sps = request.allAssets;
                    if (null != sps)
                    {
                        atlas = new SpriteAtlas();
                        for (int i = sps.Length - 1; i >= 0; i--)
                        {
                            atlas.AddSprite(sps[i].name, sps[i] as Sprite);
                        }
                        loadedRes = new LoadedRes();
                        loadedRes.sprites = atlas;
                    }
                    callBack(loadedRes);
                }
                else
                {
                    UnityEngine.Object tempObj = request.asset;
                    if (null != tempObj)
                    {
                        loadedRes = new LoadedRes();
                        loadedRes.obj = tempObj;
                    }
                    callBack(loadedRes);
                }
                CacheRes(abName, resName, loadedRes, options);
            }
            else
            {
                callBack(loadedRes);
            }
        }
        /// <summary>
        /// 缓存资源
        /// </summary>
        /// <param name="abName"></param>
        /// <param name="resName"></param>
        private void CacheRes(string abName, string resName, LoadedRes res, LoadOptions options)
        {
            if (null != res)
            {
                res.options = options;
                dicRes[abName + "-" + resName] = res;
            }

        }
        /// <summary>
        /// 获取缓存资源
        /// </summary>
        /// <param name="abName"></param>
        /// <param name="resName"></param>
        /// <returns></returns>
        private LoadedRes GetCachedRes(string abName, string resName)
        {
            LoadedRes resTemp = null;
            dicRes.TryGetValue(abName + "-" + resName, out resTemp);
            return resTemp;
        }


        /// <summary>
        /// 自动清理缓存资源
        /// </summary>
        private void AutoClearCachedRes()
        {
            if (Time.realtimeSinceStartup - lastClearTime >= CLEAR_CYCLE)
            {
                List<string> key2Remove = new List<string>();
                foreach (var item in dicRes)
                {
                    if (item.Value.CanAutoUnLoad() && (item.Value.options & LoadOptions.DontAutoUnload) != LoadOptions.DontAutoUnload)
                    {
                        key2Remove.Add(item.Key);
                    }
                }
                for (int i = 0; i < key2Remove.Count; i++)
                {
                    dicRes.Remove(key2Remove[i]);
                }
                lastClearTime = Time.realtimeSinceStartup;
            }
            Invoke("AutoClearCachedRes", CLEAR_CYCLE);
        }


        /// <summary>
        /// 释放内存
        /// </summary>
        private void FreeMemory()
        {
            lastClearTime = Time.realtimeSinceStartup;
            List<string> key2Remove = new List<string>();
            foreach (var item in dicRes)
            {
                if ((item.Value.options & LoadOptions.DontAutoUnload) != LoadOptions.DontAutoUnload)
                {
                    key2Remove.Add(item.Key);
                }
            }
            for (int i = 0; i < key2Remove.Count; i++)
            {
                dicRes.Remove(key2Remove[i]);
            }
        }

        /// <summary>
        /// 低内存回调
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void OnLowMemory()
        {
            if (Time.realtimeSinceStartup - m_LastOnLowMemeoryEventTime > 15f)
            {
                m_LastOnLowMemeoryEventTime = Time.realtimeSinceStartup;
                P.E("ResourceComponent OnLowMemeory 调用");
                m_lowMemory?.Invoke();
                FreeMemory();
                Resources.UnloadUnusedAssets();
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
        }
    }
}
